package com.abel.bigwater.kmgw

import com.abel.bigmeter.service.MeterParam
import com.abel.bigwater.data.BcdString
import com.abel.bigwater.dataserver.model.ServiceConfig
import com.abel.bigwater.dataserver.model.ewide.*
import com.abel.bigwater.kmgw.dao.RtuDao
import com.abel.bigwater.mapper.DataMapper
import com.abel.bigwater.mapper.MeterMapper
import com.abel.bigwater.mapper.RtuMapper
import com.abel.bigwater.model.BwData
import com.abel.bigwater.model.BwRtu
import com.abel.bigwater.model.BwRtuLog
import com.alibaba.fastjson.JSON
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelInboundHandlerAdapter
import io.netty.channel.socket.DatagramPacket
import org.slf4j.LoggerFactory
import java.util.*

open class EwideServerHandler(var dataMapper: DataMapper?, var meterMapper: MeterMapper?,
                              var rtuMapper: RtuMapper?, var rtuDao: RtuDao?, val config: ServiceConfig) :
        ChannelInboundHandlerAdapter() {
    override fun channelActive(ctx: ChannelHandlerContext?) {
        lgr.info("Got connection from ${ctx?.channel()?.remoteAddress()}")
    }

    override fun channelRead(ctx: ChannelHandlerContext?, msg: Any?) {
        lgr.info("$msg from ${ctx?.channel()?.remoteAddress()}")

        when {
            msg is KmingPacket -> {
                // reply back
                handleKmingPacket(ctx, msg)
            }

            msg is DatagramPacket -> {
                val buf = msg.content()
                val p = KmingPacket().apply {
                    this.streamType = "UDP"
                    this.server = "EW_UDP:${msg.recipient()}"
                    this.device = "EW_UDP:${msg.sender()}"
                    this.text = BcdString.hexString(buf, withBlank = true)
                }

                lgr.info("about to parse from ${p.device}: ${p.text}")
                if (!p.parseBuf(buf)) {
                    lgr.error("fail to parse udp from ${p.device}")
                    return
                }

                handleKmingPacket(ctx, p, msg)
            }

            else -> lgr.warn("other packet type: ${msg?.javaClass}")
        }
    }

    internal fun handleKmingPacket(ctx: ChannelHandlerContext?, msg: KmingPacket, udp: DatagramPacket? = null) {
        when (msg.data) {
            // 不关闭，等待20秒
            is TermShutdownData -> {
                val d1 = KmingRepondShutdown().apply {
                    this.sessionReponseType = SessionReponseType.Success
                }

                val reply = KmingPacket().apply {
                    data = d1
                    termCode = msg.termCode
                    this.deviceStatus = msg.deviceStatus
                    this.frameNo = msg.frameNo
                }

                val buf = ctx!!.alloc().buffer().apply { reply.pack(this) }
                val rstr = BcdString.hexString(buf, withBlank = true)
                ctx.writeAndFlush(if (udp == null) buf else DatagramPacket(buf, udp.sender(), udp.recipient()))

                lgr.info("reply shutdown: $rstr (${JSON.toJSONString(reply)})")
            }

            // 请求上传
            is TermUploadReqData -> {
                val d1 = KmingUploadReqData().apply {
                    this.backTime = Date()
                    this.respondType = SessionReponseType.Success
                }

                val reply = KmingPacket().apply {
                    data = d1
                    termCode = msg.termCode
                    this.deviceStatus = msg.deviceStatus
                    this.frameNo = msg.frameNo
                }

                val buf = ctx!!.alloc().buffer().apply { reply.pack(this) }
                val rstr = BcdString.hexString(buf, withBlank = true)
                ctx.writeAndFlush(if (udp == null) buf else DatagramPacket(buf, udp.sender(), udp.recipient()))

                lgr.info("reply upload request: $rstr (${JSON.toJSONString(reply)})")
            }

            // 上传数据
            is TermSendData -> {
                try {
                    handleSendData(msg)

                    val d1 = KmingResponseData(KmingDataType.TermSendDataReply).apply {
                        sessionReponseType = SessionReponseType.Success
                    }

                    val reply = KmingPacket().apply {
                        data = d1
                        termCode = msg.termCode
                        this.deviceStatus = msg.deviceStatus
                        this.frameNo = msg.frameNo
                    }

                    val buf = ctx!!.alloc().buffer()
                    reply.pack(buf)
                    val rstr = BcdString.hexString(buf, withBlank = true)
                    ctx.writeAndFlush(if (udp == null) buf else DatagramPacket(buf, udp.sender(), udp.recipient()))

                    lgr.info("reply data: $rstr (${JSON.toJSONString(reply)})")
                } catch (ex: Exception) {
                    val d1 = KmingResponseData(KmingDataType.TermSendDataReply).apply {
                        sessionReponseType = SessionReponseType.Failure
                    }

                    val reply = KmingPacket().apply {
                        data = d1
                        termCode = msg.termCode
                        this.deviceStatus = msg.deviceStatus
                        this.frameNo = msg.frameNo
                    }

                    val buf = ctx!!.alloc().buffer()
                    reply.pack(buf)
                    val rstr = BcdString.hexString(buf, withBlank = true)
                    ctx.writeAndFlush(if (udp == null) buf else DatagramPacket(buf, udp.sender(), udp.recipient()))

                    lgr.error("reply data failure: $rstr (${JSON.toJSONString(reply)})", ex)
                }
            }

            is TermBurstData -> {
                handleBurst(msg)

                val d1 = KmingResponseData(KmingDataType.TermBurst).apply {
                    sessionReponseType = SessionReponseType.Success
                }

                val reply = KmingPacket().apply {
                    data = d1
                    termCode = msg.termCode
                    this.deviceStatus = msg.deviceStatus
                    this.frameNo = msg.frameNo
                }

                val buf = ctx!!.alloc().buffer()
                reply.pack(buf)
                val rstr = BcdString.hexString(buf, withBlank = true)
                ctx.writeAndFlush(if (udp == null) buf else DatagramPacket(buf, udp.sender(), udp.recipient()))

                lgr.error("reply burst: $rstr (${JSON.toJSONString(reply)})")
            }
        }
    }

    private fun handleBurst(msg: KmingPacket) {
        val data = msg.data as TermBurstData
        val dev = rtuDao!!.fetchRtu(msg.termCode.fmt!!, config.firmId!!)
        val rlog = BwRtuLog().apply {
            this.remoteServer = msg.server
            this.rtuId = msg.termCode.fmt
            this.meterId = msg.termCode.fmt

            this.firmId = dev?.firmId ?: config.firmId
            this.logCmd = "EVENT"
            this.logLen = msg.pduLen
            this.logTime = Date()
            this.logText = msg.text?.take(200)
        }
        rtuMapper!!.addRtuLog(rlog)

        val rtu = BwRtu().apply {
            this.rtuId = msg.termCode.fmt
            this.firmId = dev?.firmId ?: config.firmId
            this.meterId = msg.termCode.fmt

            this.lastTime = Date()
            this.lastCmd = "EVENT"
            this.lastText = msg.text!!.take(200)
            this.lastDataTime = Date()
        }
        if (1 > rtuMapper!!.updateRtu(rtu)) {
            rtu.also {
                it.firstTime = Date()
                it.firstCmd = "EVENT"
                it.firstText = msg.text?.take(40)
            }

            rtuMapper!!.addRtu(rtu)
        }
    }

    /**
     * handle send data.
     */
    private fun handleSendData(msg: KmingPacket) {
        val data = msg.data as TermSendData
        val channelStr = data.channelDataList.joinToString { it.channelList.joinToString { it2 -> it2.code.toString(16) } }
        var dev = rtuDao!!.fetchRtu(msg.termCode.fmt!!, config.firmId!!)
        val rlog = BwRtuLog().apply {
            this.remoteServer = msg.server
            this.rtuId = msg.termCode.fmt
            this.meterId = msg.termCode.fmt

            this.firmId = dev?.firmId ?: config.firmId
            this.logCmd = "DATA"
            this.logLen = msg.pduLen
            this.logTime = Date()
            this.logText = msg.text!!.take(200)
            this.logComment = channelStr.take(40)

            this.forwardReading = data.channelDataList.lastOrNull()?.dataList?.firstOrNull()?.toDouble()
        }
        rtuMapper!!.addRtuLog(rlog)

        val rtu = BwRtu().apply {
            this.rtuId = msg.termCode.fmt
            this.firmId = dev?.firmId ?: config.firmId
            this.meterId = msg.termCode.fmt

            this.lastTime = Date()
            this.lastCmd = "DATA"
            this.lastText = msg.text!!.take(200)
            this.stateDesc = channelStr.take(40)
            this.lastDataTime = Date()
            this.forwardReading = data.channelDataList.lastOrNull()?.dataList?.firstOrNull()?.toDouble()
            this.literPulse = 1000
        }
        if (1 > rtuMapper!!.updateRtu(rtu)) {
            rtu.also {
                it.firstTime = Date()
                it.firstCmd = "DATA"
                it.firstText = msg.text?.take(40)
            }

            rtuMapper!!.addRtu(rtu)
        }

        // channels
        for (row in data.channelDataList) {
            val map = row.buildMap()

            for (i in 0 until row.channelCount!!) {
                val ch = row.channelList[i]
                val d1 = row.dataList[i]

                val _extId = "${msg.termCode.fmt}-${ch.code.toString(16)}"
                dev = rtuDao!!.fetchRtu(_extId, config.firmId!!)
                val m1 = meterMapper?.selectMeter(MeterParam(extId = _extId))?.firstOrNull()
                val _pulse = if (m1?.meterPulse ?: 0.0 < 1.0) 100.0 else m1!!.meterPulse!!

                val bd = BwData().apply {
                    extId = _extId
                    sampleTime = row.startTime
                    literPulse = if (ch in arrayOf(KmingChannelType.Flow4851, KmingChannelType.Flow4852)) 1 else _pulse.toShort()
                    forwardDigits = d1.toDouble()
                    firmId = dev?.firmId ?: config.firmId
                }

                // 压力
                if (dev?.pressureChannel ?: 0 > 0) {
                    bd.pressureDigits = map[KmingChannelType.findByCode(dev!!.pressureChannel!!)]?.toDouble()
                    bd.pressure = bd.pressureDigits?.div(10.0)
                } else {
                    bd.pressureDigits = map[KmingChannelType.Pressure1]?.toDouble()
                    if (bd.pressureDigits == null) {
                        bd.pressureDigits = map[KmingChannelType.Pressure2]?.toDouble()
                    }
                    bd.pressure = bd.pressureDigits?.div(10.0)
                }

                if (1 > dataMapper!!.updateRealtimeByValue(bd)) {
                    dataMapper!!.insertRealtime(bd)
                }
            }
        }
    }

    override fun exceptionCaught(ctx: ChannelHandlerContext?, cause: Throwable?) {
        lgr.error("try to reset connection caused by ${cause?.message} from ${ctx?.channel()?.remoteAddress()}", cause)
        ctx?.close()
    }

    companion object {
        private val lgr = LoggerFactory.getLogger(EwideServerHandler::class.java)
    }

}
